/**
 * @copyright 2015 Indie Semiconductor.
 *
 * This file is proprietary to Indie Semiconductor.
 * All rights reserved. Reproduction or distribution, in whole
 * or in part, is forbidden except by express written permission
 * of Indie Semiconductor.
 *
 * @file lin_device.c
 */

#include <stdint.h>
#include <string.h>
#include "rugby.h"
#include "hwcfg.h"
#include "lin_stack.h"
#include "lin_device.h"
#include "timer_device.h"
#include "errno.h"


lin_cb_func_t lins_callback = NULL;
lin_cb_func_t linm_callback = NULL;

uint8_t deviceFrameTableSize = 0;
LIN_Device_Frame_t *deviceFrameTable = NULL;

typedef struct {
    uint16_t divider;
    uint16_t multiplier;
    uint8_t prescale;
} lin_speed_setting_t;
/**********************************************************************  
 *    Local definitions
 *********************************************************************/   

/**
 * At 16MHz system clock, the clock divider settings for each LIN speed.
 */
static const lin_speed_setting_t lin_speed_map[E_LIN_BAUDRATE_MAX + 1] = {
    [E_LIN_BAUDRATE_9600] = {.divider = 200,  .multiplier = 2, .prescale = 1},
    [E_LIN_BAUDRATE_19200] = {.divider = 200, .multiplier = 0, .prescale = 1},// slave use this
};


 void lins_SetTiming(LIN_BaudRate_t BaudRate)
{
    LINS_SFRS->BTDIV07          = lin_speed_map[BaudRate].divider & 0xFF;
    LINS_SFRS->BITTIME.BTDIV8   = lin_speed_map[BaudRate].divider >> 8;
    LINS_SFRS->BITTIME.PRESCL   = lin_speed_map[BaudRate].prescale;
    
    //configure the Bit Rate for 19200Hz
//    LINS_SFRS->BTDIV07 = 0xC8;  
//    LINS_SFRS->BITTIME.BTDIV8 = 0x0;  
//    LINS_SFRS->BITTIME.PRESCL = 0x1; 
}


int8_t LINS_WriteBuffer(uint8_t *buff, uint8_t dataLength)
{
    if (dataLength > 8){
        return -1;
    }
    for (uint8_t i = 0; i < dataLength; i++){
        LINS_SFRS->DATABUFF[i].DATA = buff[i];
    }
    return 0;
}


int8_t LINS_ReadBuffer(uint8_t *buff, uint8_t dataLength)
{
    if (dataLength > 8){
        return -1;
    }
    for (uint8_t i = 0; i < dataLength; i++){
        buff[i] = LINS_SFRS->DATABUFF[i].DATA;
    }
    return 0;
}



void LINS_Init(void)
{
    /* Config GPIO to LIN mode, enable transmission */

    
//    IOCTRLA_SFRS->LIN.LINS_HWMODE       = 1;     // Hardware Mode Enabled. LIN slave peripheral writes/read the LIN I/O pin.
//    IOCTRLA_SFRS->LIN.LINS_ENA          = 1;     // Enable LIN
//    IOCTRLA_SFRS->LIN.LINS_RXENA        = 1;     // LIN receive enable.
//    IOCTRLA_SFRS->LIN.LINS_TXENA        = 1;     // LIN transmit enable.
//    IOCTRLA_SFRS->LIN.LINS_PU30K_ENA    = 1;     // LIN 30K pullup enable.
//    IOCTRLA_SFRS->LIN.SWON = 1;         // 1: disable Master function, 0: enable Master function on LINOUT pins
    IOCTRLA_SFRS->LIN1.HWMODE = 1;
    IOCTRLA_SFRS->LINLP.BIAS1 = 1;              //LIN1 BIAS ENABLE
    IOCTRLA_SFRS->LIN1.RXENA = 1;
    IOCTRLA_SFRS->LIN1.TXPOL = 1;               //LIN1 transmit polarity
    IOCTRLA_SFRS->LIN1.SPU30K = 1;              //LIN Slave Pullup
    IOCTRLA_SFRS->LINMUX.BYPASS = 1;            //LIN Bypass Mode
  
    LINS_SFRS->BUSTIME.BUSINACTIVE      = E_LIN_TIME_INACTIVE_SEC_4;
    LINS_SFRS->BUSTIME.WUPREPEAT        = E_LIN_TIME_WAKEUP_REPEAT_MS_180;
    LINS_SFRS->CTRL.RSTERR              = 1;    //reset error
    LINS_SFRS->CTRL.RSTINT              = 1;    //reset interrupt
    
    lins_SetTiming(E_LIN_BAUDRATE_19200);       // set the maximum supported baud rate

}


void LINS_RegisterServices(LIN_Device_Frame_t *lin_cmd_table, uint8_t size)
{
    deviceFrameTable = lin_cmd_table;
    deviceFrameTableSize = size;
}

void LINS_RegisterLINS_IRQ(lin_cb_func_t cb)
{
    lins_callback = cb;
    NVIC_EnableIRQ(LIN_Slave_IRQn);
}


void LINS_UnRegisterLINS_IRQ(void)
{
    lins_callback = NULL;
    NVIC_DisableIRQ(LIN_Slave_IRQn);
}

/**
 * @brief Check if the message id is in the frame list
 *
 * @param dev The pointer to LIN device.
 * @param id The ID of received frame.
 * @return the index of the ID the the supported frame list, or -1 if not supported.
 */
int32_t is_valid_frame(LIN_Device_Frame_t *frameTable, uint8_t id)
{
    uint8_t i;
    if ((id == LIN_FRAME_ID_UNASSIGN) || (id == LIN_FRAME_ID_DONOTCARE)){
        return -1;
    }
    for (i = 0; i < deviceFrameTableSize; i++){
        if (frameTable[i].frame_id == id){
              return i;
        }
    }
    return -1;
}

void LIN_Slave_Handler(void)
{
    static int32_t index = 0;
    uint8_t status = (uint8_t)LINS_SFRS->STATUS.WORD;

    if (status & E_LIN_STATUS_DATA_REQ){
        index = is_valid_frame(deviceFrameTable, LINS_SFRS->ID);
        if (index >= 0){
            if (deviceFrameTable[index].msg_type == LIN_MSG_TYPE_RX){
                LINS_SFRS->CTRL.TRANSMIT = 0;// it's a data receive operation
                LINS_SFRS->DL.LENGTH = deviceFrameTable[index].length;
                LINS_SFRS->DL.ENHCHK = deviceFrameTable[index].checksum;
                LINS_SFRS->CTRL.DATAACK = 1;
            }else{
                if (deviceFrameTable[index].responseValid){
                    deviceFrameTable[index].responseValid = false;
                    LINS_SFRS->CTRL.TRANSMIT = 1;// it's data request operation
                    LINS_SFRS->DL.LENGTH = deviceFrameTable[index].length;
                    LINS_SFRS->DL.ENHCHK = deviceFrameTable[index].checksum;
                    LINS_WriteBuffer(deviceFrameTable[index].data, deviceFrameTable[index].length);
                    LINS_SFRS->CTRL.DATAACK = 1;
                }
            }
        }else{
            LINS_SFRS->CTRL.STOP = 1;
        }
    }else if (status & E_LIN_STATUS_ERROR){
    }else if (status & E_LIN_STATUS_IDLE_TIMEOUT){
        LINS_SFRS->CTRL.SLEEP = 1;
    }else if (status & E_LIN_STATUS_WAKEUP){
    }else if (status & E_LIN_STATUS_COMPLETE){//receive finished
        LINS_ReadBuffer(deviceFrameTable[index].data, deviceFrameTable[index].length);
        if(lins_callback){
            lins_callback(&deviceFrameTable[index]);
        }
    }
    LINS_SFRS->CTRL.RSTERR  = 1;    //reset error
    LINS_SFRS->CTRL.RSTINT  = 1;    //reset interrupt
}




int8_t LINM_WriteBuffer(uint8_t *buff, uint8_t dataLength)
{
    if (dataLength > 8){
        return -1;
    }
    for (uint8_t i = 0; i < dataLength; i++){
        LINM_SFRS->DATABUFF[i].DATA = buff[i];
    }
    return 0;
}


int8_t LINM_ReadBuffer(uint8_t *buff, uint8_t dataLength)
{
    if (dataLength > 8){
        return -1;
    }
    for (uint8_t i = 0; i < dataLength; i++){
        buff[i] = LINM_SFRS->DATABUFF[i].DATA;
    }
    return 0;
}


void linm_SetTiming(LIN_BaudRate_t BaudRate)
{
    LINM_SFRS->BTDIV07 = lin_speed_map[BaudRate].divider & 0xFF;
    LINM_SFRS->BITTIME.BTDIV8 = lin_speed_map[BaudRate].divider >> 8;
    LINM_SFRS->BITTIME.BTMULT = lin_speed_map[BaudRate].multiplier;
    LINM_SFRS->BITTIME.PRESCL = lin_speed_map[BaudRate].prescale;
}


void LINM_Init(void)
{
//    IOCTRLA_SFRS->LIN.LINM_HWMODE       = 1;    //Hardware Mode Enabled. LIN slave peripheral writes/read the LIN I/O pin.
//    IOCTRLA_SFRS->LIN.LINM_ENA          = 1;    //Enable LIN
//    IOCTRLA_SFRS->LIN.LINM_RXENA        = 1;    //LIN receive enable.
//    IOCTRLA_SFRS->LIN.LINM_TXENA        = 1;    //LIN transmit enable.
//    IOCTRLA_SFRS->LIN.LINM_PU1K_ENA     = 1;    //LIN 1K pullup enable.
    IOCTRLA_SFRS->LIN2.HWMODE = 1;
    IOCTRLA_SFRS->LINLP.BIAS2 = 1;
    IOCTRLA_SFRS->LIN2.RXENA  = 1;
    IOCTRLA_SFRS->LIN2.TXPOL  = 1;  //As the TXD connects to PullDown in trx
    IOCTRLA_SFRS->LIN2.MPU1K   = 1;
//    IOCTRLA_SFRS->LIN.SWON = 0;         // 1: disable Master function, 0: enable Master function on LINOUT pin
    IOCTRLA_SFRS->LINMUX.BYPASS = 1;//

    LINM_SFRS->CTRL.RSTERR              = 1;
    LINM_SFRS->CTRL.RSTINT              = 1;
    
    linm_SetTiming(E_LIN_BAUDRATE_19200);
}

void LINM_RegisterLINM_IRQ(lin_cb_func_t cb)
{
    linm_callback = cb;
    NVIC_EnableIRQ(LIN_Master_IRQn);
}


void LINM_UnRegisterLINM_IRQ(void)
{
    linm_callback = NULL;
    NVIC_DisableIRQ(LIN_Master_IRQn);
}

int8_t LINM_SendFrame(LIN_Device_Frame_t *frame)
{
    LINM_SFRS->ID        = frame->frame_id;
    if (frame->msg_type == LIN_MSG_TYPE_TX){
        LINM_SFRS->DL.LENGTH = frame->length;
        LINM_SFRS->CTRL.TRANSMIT = 1;
    }else{
        LINM_SFRS->DL.LENGTH = frame->length;
        LINM_SFRS->CTRL.TRANSMIT = 0;
    }
    LINM_SFRS->DL.ENHCHK = frame->checksum;
    
    LINM_WriteBuffer(frame->data, frame->length);
    LINM_SFRS->CTRL.STARTREQ = 1;
    
    return 0;
}

LIN_Device_Frame_t linmFrame;

void LIN_Master_Handler(void)
{
    uint8_t status   = (uint8_t)LINM_SFRS->STATUS.WORD;
    
    if (status & E_LIN_STATUS_ERROR){
      
    }else if(status & E_LIN_STATUS_WAKEUP){
        
    }else if(status & E_LIN_STATUS_COMPLETE){
        linmFrame.frame_id = LINM_SFRS->ID;
        linmFrame.msg_type = LIN_RX;
    }
    if ((status & E_LIN_STATUS_COMPLETE) || (status & E_LIN_STATUS_DATA_REQ) ){
        linmFrame.length   = LINM_SFRS->DL.LENGTH;
        LINM_ReadBuffer(linmFrame.data, LIN_BUFF_SIZE);
        if (linm_callback){
            linm_callback(&linmFrame);
        }
    }
    LINM_SFRS->CTRL.RSTERR              = 1;    //reset error
    LINM_SFRS->CTRL.RSTINT              = 1;    //reset interrupt
}

void WULINM_Handler(void)
{
  
}




